day 5¶

このノートブックの実行例はこちら(HTML版)で確認できます


0. はじめに¶

ページ上部のメニューバーにある Kernel メニューをクリックし、プルダウンメニューから [Change Kernel ...] を選び、gssm2024:Python を選択してください。

No description has been provided for this image

ノートブック上部の右隅に表示されたカーネル名が gssm2024:Python になっていることを確認してください。

No description has been provided for this image

1. テキスト分析 (実践編)¶

1.0 事前準備¶

1.0.1 定義済み関数の読み込み¶

以下のセルを修正せずに実行してください

In [1]:
import warnings
warnings.simplefilter('ignore')

import gssm_utils

%matplotlib inline

1.0.1 データのダウンロード (前回ダウンロード済みのためスキップ)¶

以下のデータがダウンロード済みです

ファイル名 件数 データセット 備考
rakuten-1000-2023-2024.xlsx.zip 10,000 •レジャー+ビジネスの 10エリア
•エリアごと 1,000件 (ランダムサンプリング)
•期間: 2023/1~2024 GW明け
本講義の全体を通して使用する
In [2]:
# もし、再度ダウンロードが必要な場合は残りの行のコメントマーク「#」を除去して、このセルを再実行してください

# FILE_ID = "1EeCuDrfKdlsMxG9p3Ot7TIxfV9_f2smY"
# !gdown --id {FILE_ID}
# !unzip -o rakuten-1000-2023-2024.xlsx.zip

1.0.2 データの読み込み (DataFrame型)¶

In [3]:
import numpy as np
import pandas as pd

all_df = pd.read_excel("rakuten-1000-2023-2024.xlsx")
print(all_df.shape)
display(all_df.head())
(10000, 18)
カテゴリー エリア 施設番号 施設名 コメント 総合 サービス 立地 部屋 設備・アメニティ 風呂 食事 旅行の目的 同伴者 宿泊年月 投稿者 年代 性別
0 A_レジャー 01_登別 80732 登別カルルス温泉 湯元オロフレ荘 彼のリクエストでカルルス温泉に宿泊させていただきました。北海道ラブ割利用でお得に宿泊できる分... 5 5 4 5 5.0 5.0 5.0 レジャー 恋人 45231 投稿者 na na
1 A_レジャー 01_登別 149334 天然温泉 幸鐘の湯 ドーミーイン東室蘭(ドーミーイン・御宿野乃 ホテルズグループ) 室蘭方面の旅行の時には何度か宿泊しています。ドーミーインのサービスはとても良くて大好きです。... 4 5 5 2 5.0 5.0 5.0 レジャー 家族 45413 投稿者 na na
2 A_レジャー 01_登別 109022 心のリゾート 海の別邸ふる川 もう一度行きたくなる宿でした。部屋もワンランクアップしていただき感謝しています。お風呂も最高... 4 3 5 5 4.0 5.0 3.0 レジャー 一人 45261 gaku0713 60代 男性
3 A_レジャー 01_登別 7506 ホテルニューバジェット室蘭 居心地良く過ごせました 4 4 4 4 4.0 4.0 3.0 レジャー 一人 45200 投稿者 na na
4 A_レジャー 01_登別 37380 登別温泉 旅亭 花ゆら 事前カード精算で露天風呂付特別和洋室を予約しましたが別の和室に案内されました。すぐ違うことに... 1 2 4 2 2.0 4.0 3.0 レジャー 家族 45323 投稿者 na na

1.0.3 「文書-抽出語」表 を作成する¶

コメント列から単語を抽出する (単語を品詞「名詞」「形容詞」「未知語」で絞り込む)

In [4]:
# 必要ライブラリのインポート
from collections import defaultdict
import MeCab

# mecab の初期化
tagger = MeCab.Tagger("-r ../tools/usr/local/etc/mecabrc --unk-feature 未知語")

# 単語頻度辞書の初期化
word_counts = defaultdict(lambda: 0)

# 抽出語情報リストの初期化
words = []

# 半角->全角変換マクロを定義する
ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
HAN2ZEN = str.maketrans(HAN, ZEN)

# ストップワードを定義する
# stopwords = ['する', 'ある', 'ない', 'いう', 'もの', 'こと', 'よう', 'なる', 'ほう']
stopwords = ["湯畑"]

# データ1行ごとのループ
for index, row in all_df.iterrows():

    # 半角->全角変換した後で, mecab で解析する
    node = tagger.parseToNode(row["コメント"].translate(HAN2ZEN))

    # 形態素ごとのループ
    while node:
        # 解析結果を要素ごとにバラす
        features = node.feature.split(',')

        # 品詞1 を取り出す
        pos1 = features[0]

        # 品詞2 を取り出す
        pos2 = features[1] if len(features) > 1 else ""

        # 原形 を取り出す
        base = features[6] if len(features) > 6 else None

        # 原型がストップワードに含まれない単語のみ抽出する
        if base not in stopwords:

            # 「名詞-一般」
            if (pos1 == "名詞" and pos2 == "一般"):
                base = base if base is not None else node.surface
                postag = "名詞"
                key = (base, postag)

                # 単語頻度辞書をカウントアップする
                word_counts[key] += 1

                # 抽出語情報をリストに追加する
                words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])

            # 「形容動詞」
            elif (pos1 == "名詞" and pos2 == "形容動詞語幹"):
                base = base if base is not None else node.surface
                base = f"{base}"
                postag = "形容動詞"
                key = (base, postag)

                # 単語頻度辞書をカウントアップする
                word_counts[key] += 1

                # 抽出語情報をリストに追加する
                words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])

            # 「形容詞」
            elif pos1 == "形容詞":
                base = base if base is not None else node.surface
                postag = "形容詞"
                key = (base, postag)

                # 単語頻度辞書をカウントアップする
                word_counts[key] += 1

                # 抽出語情報をリストに追加する
                words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])

            # 「未知語」
            elif pos1 == "未知語":
                base = base if base is not None else node.surface
                postag = "未知語"
                key = (base, postag)

                # 単語頻度辞書をカウントアップする
                word_counts[key] += 1

                # 抽出語情報をリストに追加する
                words.append([index + 1, base, postag, row["カテゴリー"], row["エリア"], key])

        # 次の形態素へ
        node = node.next

# DataFrme 型に整える
columns = [
    "文書ID",
    # "単語ID",
    "表層",
    "品詞",
    "カテゴリー",
    "エリア",
    "dict_key",
]
docs_df = pd.DataFrame(words, columns=columns)

# DataFrame を表示する
print(docs_df.shape)
display(docs_df.head())
(158822, 6)
文書ID 表層 品詞 カテゴリー エリア dict_key
0 1 リクエスト 名詞 A_レジャー 01_登別 (リクエスト, 名詞)
1 1 温泉 名詞 A_レジャー 01_登別 (温泉, 名詞)
2 1 ラブ 名詞 A_レジャー 01_登別 (ラブ, 名詞)
3 1 割 名詞 A_レジャー 01_登別 (割, 名詞)
4 1 得 名詞 A_レジャー 01_登別 (得, 名詞)

抽出語の出現頻度をカウントする

In [5]:
# 「文書-抽出語」 表から単語の出現回数をカウントする
word_list = []
for i, (k, v) in enumerate(sorted(word_counts.items(), key=lambda x:x[1], reverse=True)):
    word_list.append((i, k[0], v, k))

# DataFrame 型に整える
columns = [
    "単語ID",
    "表層",
    "出現頻度",
    "dict_key"
]

# DataFrame を表示する
word_counts_df = pd.DataFrame(word_list, columns=columns)
print(word_counts_df.shape)
display(word_counts_df.head(10))
(9060, 4)
単語ID 表層 出現頻度 dict_key
0 0 部屋 6768 (部屋, 名詞)
1 1 良い 5397 (良い, 形容詞)
2 2 ホテル 3001 (ホテル, 名詞)
3 3 風呂 2692 (風呂, 名詞)
4 4 ない 2392 (ない, 形容詞)
5 5 美味しい 2294 (美味しい, 形容詞)
6 6 温泉 1854 (温泉, 名詞)
7 7 スタッフ 1677 (スタッフ, 名詞)
8 8 立地 1532 (立地, 名詞)
9 9 よい 1517 (よい, 形容詞)

1.2 カテゴリーやエリアごとのユーザーの注目ポイントの評価の違いを見つける¶

1.2.1 「文書-抽出語」表の作成¶

「文書-抽出語」表を作成する (出現回数 Top 1000語)

In [6]:
# 「単語出現回数」 表から出現回数Top 1000語のみ抽出する
word_counts_1000_df = word_counts_df[0:1000]

# 「文書-抽出語」 表も出現回数Top 150語のみに絞り込む
merged_df = pd.merge(docs_df, word_counts_1000_df, how="inner", on="dict_key", suffixes=["", "_right"])
docs_1000_df = merged_df[["文書ID", "単語ID", "表層", "品詞", "カテゴリー", "エリア", "dict_key"]]

# 「カテゴリー,エリア」でクロス集計する
cross_1000_df = pd.crosstab(
    [
        docs_1000_df['カテゴリー'], 
        docs_1000_df['エリア'], 
        docs_1000_df['文書ID']
    ],
    docs_1000_df['単語ID'], margins=False
)
cross_1000_df.columns = word_counts_1000_df["表層"]

「文書-抽出語」表を {0,1} に変換する

In [7]:
# 「文書-抽出語」 表を {0,1} に変換する
cross_1000_df[cross_1000_df > 0] = 1

# DataFrame を表示する
print(cross_1000_df.shape)
display(cross_1000_df)
(9914, 1000)
表層 部屋 良い ホテル 風呂 ない 美味しい 温泉 スタッフ 立地 よい ... ほこり 豊か カラオケ 支配人 頻繁 感覚 枕元 コンサート 店舗 人柄
カテゴリー エリア 文書ID
A_レジャー 01_登別 1 1 0 0 0 0 1 1 1 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 1 1 0 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
3 1 0 0 1 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
4 0 1 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
5 1 0 0 1 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
B_ビジネス 10_福岡 9996 1 0 1 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
9997 1 0 0 1 0 0 0 0 1 0 ... 0 0 0 0 0 0 0 0 0 0
9998 0 0 1 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
9999 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
10000 0 1 1 0 0 0 1 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

9914 rows × 1000 columns

1.2.2 共起行列を作成する (外部変数-抽出語)¶

In [8]:
# 「カテゴリー」のクロス集計と「エリア」のクロス集計を連結する
aggregate_df = pd.concat(
    [
        cross_1000_df.groupby(level='カテゴリー').sum(),
        cross_1000_df.groupby(level='エリア').sum()
    ]
)

# DataFrame を表示する
print(aggregate_df.shape)
display(aggregate_df)
(12, 1000)
表層 部屋 良い ホテル 風呂 ない 美味しい 温泉 スタッフ 立地 よい ... ほこり 豊か カラオケ 支配人 頻繁 感覚 枕元 コンサート 店舗 人柄
A_レジャー 2367 2134 789 1498 957 1486 1290 916 548 677 ... 4 16 16 13 11 12 5 1 1 17
B_ビジネス 2213 1705 1368 652 789 542 113 505 942 578 ... 11 4 3 3 8 9 15 18 19 3
01_登別 433 422 173 296 193 260 271 149 47 111 ... 1 1 0 0 0 0 0 0 0 2
02_草津 489 468 161 376 198 305 311 177 146 134 ... 1 3 4 3 8 1 0 0 0 5
03_箱根 544 454 158 331 199 373 234 222 69 156 ... 2 5 6 3 0 4 2 0 0 4
04_道後 408 380 228 190 157 177 231 130 184 135 ... 0 0 4 1 0 2 2 0 0 0
05_湯布院 493 410 69 305 210 371 243 238 102 141 ... 0 7 2 6 3 5 1 1 1 6
06_札幌 461 359 278 126 138 135 24 95 191 92 ... 1 1 0 1 3 4 2 4 3 1
07_名古屋 430 337 267 123 155 104 30 99 164 130 ... 1 0 1 0 1 0 4 2 5 0
08_東京 398 345 276 119 162 82 9 100 172 113 ... 3 0 0 1 0 1 5 1 1 1
09_大阪 459 316 262 139 151 100 25 104 184 122 ... 5 1 2 1 3 1 2 4 2 1
10_福岡 465 348 285 145 183 121 25 107 231 121 ... 1 2 0 0 1 3 2 7 8 0

12 rows × 1000 columns

1.2.3 Jaccard 係数を求める (外部変数-抽出語)¶

In [9]:
# 抽出語の出現回数を取得する
word_counts = cross_1000_df.sum(axis=0).values

# 属性(外部変数)出現数を取得する
attr_counts = np.hstack(
    [
        all_df.value_counts('カテゴリー').values,
        all_df.value_counts('エリア').values
    ]
)

# 共起行列の中身を Jaccard 係数に入れ替える
jaccard_attrs_df = gssm_utils.jaccard_attrs_coef(aggregate_df, attr_counts, word_counts, total=10000, conditional=False)

# DataFrame を表示する
print(jaccard_attrs_df.shape)
display(jaccard_attrs_df)
(12, 1000)
表層 部屋 良い ホテル 風呂 ない 美味しい 温泉 スタッフ 立地 よい ... ほこり 豊か カラオケ 支配人 頻繁 感覚 枕元 コンサート 店舗 人柄
A_レジャー 0.328157 0.318270 0.000000 0.265039 0.165314 0.268134 0.252298 0.166394 0.000000 0.121370 ... 0.000000 0.003197 0.003198 0.002598 0.002196 0.002396 0.000000 0.000000 0.000000 0.003398
B_ビジネス 0.000000 0.000000 0.236310 0.000000 0.000000 0.000000 0.000000 0.000000 0.169791 0.000000 ... 0.002198 0.000000 0.000000 0.000000 0.000000 0.000000 0.002997 0.003599 0.003799 0.000000
01_登別 0.000000 0.095540 0.000000 0.103714 0.075597 0.093931 0.127111 0.065581 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
02_草津 0.096052 0.107069 0.000000 0.135544 0.077708 0.112009 0.148662 0.078877 0.000000 0.063178 ... 0.000000 0.002950 0.003941 0.002962 0.007913 0.000000 0.000000 0.000000 0.000000 0.004926
03_箱根 0.108022 0.103535 0.000000 0.117418 0.078131 0.140490 0.107884 0.100955 0.000000 0.074321 ... 0.001974 0.004926 0.005923 0.002962 0.000000 0.003933 0.000000 0.000000 0.000000 0.003937
04_道後 0.000000 0.000000 0.077842 0.000000 0.000000 0.000000 0.106354 0.000000 0.079792 0.063679 ... 0.000000 0.000000 0.003941 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
05_湯布院 0.096914 0.092572 0.000000 0.107206 0.082808 0.139631 0.112500 0.109024 0.000000 0.066698 ... 0.000000 0.006910 0.001967 0.005941 0.002953 0.004921 0.000000 0.000000 0.000000 0.005917
06_札幌 0.090057 0.000000 0.096561 0.000000 0.000000 0.000000 0.000000 0.000000 0.083080 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.002953 0.003933 0.000000 0.003941 0.002950 0.000000
07_名古屋 0.000000 0.000000 0.092388 0.000000 0.000000 0.000000 0.000000 0.000000 0.070507 0.061176 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.003937 0.001967 0.004926 0.000000
08_東京 0.000000 0.000000 0.095800 0.000000 0.000000 0.000000 0.000000 0.000000 0.074202 0.000000 ... 0.002964 0.000000 0.000000 0.000000 0.000000 0.000000 0.004926 0.000000 0.000000 0.000000
09_大阪 0.089631 0.000000 0.090501 0.000000 0.000000 0.000000 0.000000 0.000000 0.079792 0.000000 ... 0.004950 0.000000 0.001967 0.000000 0.002953 0.000000 0.000000 0.003941 0.000000 0.000000
10_福岡 0.090909 0.000000 0.099234 0.000000 0.071401 0.000000 0.000000 0.000000 0.102258 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.002947 0.000000 0.006917 0.007905 0.000000

12 rows × 1000 columns

1.2.4 共起ネットワーク図¶

In [10]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline

# サブルーチン
def sort_and_plot(name, group):

    # 「カテゴリー」ごとに Jaccard 係数でソートする
    sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]

    # Jaccard 係数Top 75語をソートして抽出する
    group_cross_df = group.iloc[:,sorted_columns]

    # 共起行列を作成する
    X = group_cross_df.values
    X = csc_matrix(X)
    Xc = (X.T * X)
    Xc = np.triu(Xc.toarray())

    # 共起行列を DataFrame に整える
    group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)

    # 共起行列の中身を Jaccard 係数に入れ替える
    group_jaccard_df = gssm_utils.jaccard_coef(group_cooccur_df, group_cross_df)

    # 抽出語の出現回数を取得する
    word_counts = group_cross_df.sum(axis=0).values

    # プロットする
    ax = fig.add_subplot(4, 3, i+1)
    pyvis_plot = gssm_utils.plot_cooccur_network_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], pyvis=True, name=f"{name}.html")
    ax.set_title(name)
    display(pyvis_plot)

# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))

i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
    # サブルーチンを呼ぶ
    sort_and_plot(name, group)
    i += 1

    # エリアごとのループ
    for sub_name, sub_group in group.groupby(level='エリア'):
        # サブルーチンを呼ぶ
        sort_and_plot(sub_name, sub_group)
        i += 1

# プロットの仕上げ
plt.tight_layout()
plt.show()
A_レジャー.html
01_登別.html
02_草津.html
03_箱根.html
04_道後.html
05_湯布院.html
B_ビジネス.html
06_札幌.html
07_名古屋.html
08_東京.html
09_大阪.html
10_福岡.html
No description has been provided for this image

1.2.5 係り受け行列を作成する (抽出語-抽出語)¶

In [11]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix

# 共起行列を作成する
X = cross_1000_df.values
X = csc_matrix(X)
Xc = (X.T * X)
# 対角成分のみにする
Xc = np.triu(Xc.toarray())

# DataFrame 型に整える
cooccur_1000_df = pd.DataFrame(Xc, columns=cross_1000_df.columns, index=cross_1000_df.columns)

# チャンク(文節)から単語を取り出す
def get_words(tree, from_chunk, stopwords):

    # チャンク(文節)の開始位置を取得する
    beg = from_chunk.token_pos

    # チャンクの開始位置を取得する
    end = from_chunk.token_pos + from_chunk.token_size

    # 抽出語情報リストの初期化
    words = []

    # チャンク(文節)ごとのループ
    for i in range(beg, end):

        # チャンク中の形態素を取り出す
        token = tree.token(i)

        # 解析結果を要素ごとにバラす
        features = token.feature.split(',')

        # 品詞1 を取り出す
        pos1 = features[0]

        # 品詞2 を取り出す
        pos2 = features[1] if len(features) > 1 else ""

        # 原形 を取り出す
        base = features[6] if len(features) > 6 else None

        # 原型がストップワードに含まれない単語のみ抽出する
        if base not in stopwords:

            # 「名詞-一般」
            if (pos1 == "名詞" and pos2 == "一般"):
                base = base if base is not None else node.surface
                postag = "名詞"
                key = (base, postag)

                # 抽出語情報をリストに追加する
                words.append(key)

            # 「形容動詞」
            elif (pos1 == "名詞" and pos2 == "形容動詞語幹"):
                base = base if base is not None else node.surface
                base = f"{base}だ"
                postag = "形容動詞"
                key = (base, postag)

                # 抽出語情報をリストに追加する
                words.append(key)

            # 「形容詞」
            elif pos1 == "形容詞":
                base = base if base is not None else node.surface
                postag = "形容詞"
                key = (base, postag)

                # 抽出語情報をリストに追加する
                words.append(key)

            # 「未知語」
            elif pos1 == "未知語":
                base = base if base is not None else node.surface
                postag = "未知語"
                key = (base, postag)

                # 抽出語情報をリストに追加する
                words.append(key)

    # 抽出語情報をリストを返却する
    return words


# 必要ライブラリのインポート
import CaboCha

# cabocha の初期化
cp = CaboCha.Parser("-r ../tools/usr/local/etc/cabocharc")

# 半角->全角変換マクロを定義する
ZEN = "".join(chr(0xff01 + i) for i in range(94))
HAN = "".join(chr(0x21 + i) for i in range(94))
HAN2ZEN = str.maketrans(HAN, ZEN)

# ストップワードを定義する
# stopwords = ['する', 'ある', 'ない', 'いう', 'もの', 'こと', 'よう', 'なる', 'ほう']
stopwords = ['*']  # 原形に 「'*'」 が出力された場合に除去するため

# 係り受けペア辞書の初期化
pair_counts = defaultdict(lambda: 0)
pairs = []

# データ1行ごとのループ
for index, row in all_df.iterrows():

    # 半角->全角変換した後で, cabocha で解析する
    tree = cp.parse(row["コメント"].translate(HAN2ZEN))

    # 解析結果から空でないチャンク(文節)のリストを集める
    chunks = {}
    key = 0
    for i in range(tree.size()):
        tok = tree.token(i)
        if tok.chunk:
            chunks[key] = tok.chunk
            key += 1

    # 係り元と係り先の単語情報(原形と品詞)を集める
    for from_chunk in chunks.values():
        # 係り先がなければスキップ
        if from_chunk.link < 0:
            continue

        # 係り先のチャンク(文節)を取得する
        to_chunk = chunks[from_chunk.link]

        # 係り元の単語情報(原形と品詞)を取得する
        from_words = get_words(tree, from_chunk, stopwords)

        # 係り先の単語情報(原形と品詞)を取得する
        to_words = get_words(tree, to_chunk, stopwords)

    # 係り受けペアと頻度を収集する
    for f in from_words:
        for t in to_words:
            key = (f[0], t[0])
            pair_counts[key] += 1


# 係り受け行列を初期化する (共起行列と同じ形)
Xd = np.zeros(cooccur_1000_df.shape)

# 係り受けペアを係り受け列に変換する
for (f,t), v in pair_counts.items():
    columns = list(cooccur_1000_df.columns)
    if f in columns and t in columns:
        i = columns.index(f)
        j = columns.index(t)
        Xd[i,j] = v

# DataFrme 型に整える
dep_1000_df = pd.DataFrame(Xd, columns=cooccur_1000_df.columns, index=cooccur_1000_df.columns)
print(dep_1000_df.shape)
display(dep_1000_df.head())
(1000, 1000)
表層 部屋 良い ホテル 風呂 ない 美味しい 温泉 スタッフ 立地 よい ... ほこり 豊か カラオケ 支配人 頻繁 感覚 枕元 コンサート 店舗 人柄
表層
部屋 0.0 8.0 0.0 1.0 1.0 1.0 0.0 0.0 0.0 3.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
良い 3.0 2.0 24.0 0.0 3.0 2.0 1.0 0.0 1.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
ホテル 0.0 7.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
風呂 0.0 12.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 4.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
ない 1.0 3.0 1.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

5 rows × 1000 columns

1.2.6 係り受けネットワーク図¶

In [12]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline

# サブルーチン
def sort_and_plot(name, group, pos):

    # 「カテゴリー」ごとに Jaccard 係数でソートする
    sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]

    # Jaccard 係数Top 75語をソートして抽出する
    group_cross_df = group.iloc[:,sorted_columns]

    # 抽出語の出現回数を取得する
    word_counts = group_cross_df.sum(axis=0).values

    # 共起行列を作成する
    X = group_cross_df.values
    X = csc_matrix(X)
    Xc = (X.T * X)
    Xc = np.triu(Xc.toarray())

    # 共起行列を DataFrame に整える
    group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)

    # 係り受け行列(条件付き確率)を初期化する (共起行列と同じ形)
    Xd = np.zeros(group_cooccur_df.shape)

    # 係り受けペアを係り受け行列(条件付き確率)に変換する
    for (f,t), v in pair_counts.items():

        # 係り元と係り先の両方が列に含まれる
        columns = list(group_cooccur_df.columns)
        if f in columns and t in columns:
            i = columns.index(f)
            j = columns.index(t)

            # 条件付き確率(係り受け頻度/係り先出現回数)を求める
            Xd[i,j] = v / word_counts[i]

    # 係り受け行列(条件付き確率)を DataFrame 型に整える
    group_dependency_df = pd.DataFrame(Xd, columns=group_cross_df.columns, index=group_cross_df.columns)

    # プロットする
    ax = fig.add_subplot(4, 3, pos+1)
    pyvis_plot = gssm_utils.plot_dependency_network_ax(ax, group_dependency_df, word_counts, np.sort(group_dependency_df.values.reshape(-1))[::-1][120], pyvis=False, name=f"{name}.html")
    ax.set_title(name)
    # display(pyvis_plot)

    # 係り受けペアを条件付き確率で降順にソートし20個表示する
    Xc = group_dependency_df.values
    words = group_dependency_df.columns
    flat_indices = np.argsort(Xc.ravel())[::-1][:20]
    row_indices, col_indices = np.unravel_index(flat_indices, Xc.shape)
    for idx in range(len(flat_indices)):
        value = Xc[row_indices[idx], col_indices[idx]]
        print(f"[{name}] {words[row_indices[idx]]} - {words[col_indices[idx]]}: {value:.04f}")
    print()

# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))

i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
    # サブルーチンを呼ぶ
    sort_and_plot(name, group, i)
    i += 1

    # エリアごとのループ
    for sub_name, sub_group in group.groupby(level='エリア'):
        # サブルーチンを呼ぶ
        sort_and_plot(sub_name, sub_group, i)
        i += 1

# プロットの仕上げ
plt.tight_layout()
plt.show()
[A_レジャー] 感じ - 良い: 0.0343
[A_レジャー] 景色 - 良い: 0.0335
[A_レジャー] すごい - 良い: 0.0244
[A_レジャー] 雰囲気 - 良い: 0.0233
[A_レジャー] ご飯 - 美味しい: 0.0181
[A_レジャー] 気持ち - 良い: 0.0162
[A_レジャー] 気持ち - よい: 0.0162
[A_レジャー] 味 - 良い: 0.0160
[A_レジャー] 味 - 美味しい: 0.0160
[A_レジャー] いい - 宿: 0.0157
[A_レジャー] 皆さん - 良い: 0.0147
[A_レジャー] 優しい - 宿: 0.0136
[A_レジャー] 浴場 - 良い: 0.0124
[A_レジャー] おいしい - よい: 0.0114
[A_レジャー] いい - 感じ: 0.0112
[A_レジャー] 景色 - 最高: 0.0112
[A_レジャー] 最高 - 宿: 0.0109
[A_レジャー] 方々 - 皆さん: 0.0102
[A_レジャー] 多い - 美味しい: 0.0081
[A_レジャー] お湯 - 良い: 0.0080

[01_登別] 気分 - 悪い: 0.2857
[01_登別] 感じ - 良い: 0.1757
[01_登別] 景色 - 良い: 0.1176
[01_登別] すごい - 良い: 0.1053
[01_登別] ご飯 - 美味しい: 0.0943
[01_登別] 味 - 良い: 0.0857
[01_登別] 味 - 美味しい: 0.0857
[01_登別] 眺め - 最高: 0.0800
[01_登別] 眺め - 良い: 0.0800
[01_登別] いい - 宿: 0.0722
[01_登別] 浴場 - 良い: 0.0600
[01_登別] 最高 - 宿: 0.0547
[01_登別] いい - 感じ: 0.0515
[01_登別] 風呂 - 良い: 0.0405
[01_登別] 内容 - 悪い: 0.0400
[01_登別] 内容 - 良い: 0.0400
[01_登別] お湯 - 良い: 0.0400
[01_登別] 思い出 - 良い: 0.0400
[01_登別] お湯 - 最高: 0.0400
[01_登別] 景色 - 最高: 0.0392

[02_草津] 感じ - 良い: 0.1413
[02_草津] 雰囲気 - 良い: 0.1176
[02_草津] すごい - 良い: 0.1053
[02_草津] いい - 宿: 0.0897
[02_草津] ご飯 - 美味しい: 0.0862
[02_草津] 味 - 美味しい: 0.0769
[02_草津] 味 - 良い: 0.0769
[02_草津] いい - 感じ: 0.0641
[02_草津] 浴場 - 良い: 0.0625
[02_草津] 皆さん - 良い: 0.0588
[02_草津] おいしい - よい: 0.0577
[02_草津] 最高 - 宿: 0.0473
[02_草津] 方々 - 皆さん: 0.0444
[02_草津] 露天風呂 - 最高: 0.0396
[02_草津] 多い - 美味しい: 0.0364
[02_草津] 種類 - 少ない: 0.0339
[02_草津] 風呂 - 良い: 0.0319
[02_草津] 広い - 良い: 0.0315
[02_草津] お湯 - 良い: 0.0312
[02_草津] お湯 - 最高: 0.0312

[03_箱根] 感じ - 良い: 0.1857
[03_箱根] 景色 - 良い: 0.1250
[03_箱根] 雰囲気 - 良い: 0.0833
[03_箱根] 気持ち - 良い: 0.0750
[03_箱根] 気持ち - よい: 0.0750
[03_箱根] いい - 宿: 0.0737
[03_箱根] 最高 - 宿: 0.0660
[03_箱根] 皆さん - 良い: 0.0652
[03_箱根] ご飯 - 美味しい: 0.0633
[03_箱根] デザート - 美味しい: 0.0571
[03_箱根] 味 - 良い: 0.0556
[03_箱根] 優しい - 宿: 0.0556
[03_箱根] 味 - 美味しい: 0.0556
[03_箱根] おいしい - よい: 0.0536
[03_箱根] いい - 感じ: 0.0526
[03_箱根] 浴場 - 良い: 0.0488
[03_箱根] アメニティ - 良い: 0.0429
[03_箱根] 湯 - よい: 0.0417
[03_箱根] 景色 - 最高: 0.0417
[03_箱根] お湯 - 最高: 0.0392

[04_道後] ごはん - おいしい: 0.1429
[04_道後] バスタオル - ほしい: 0.0833
[04_道後] おいしい - よい: 0.0667
[04_道後] 浴衣 - にくい: 0.0588
[04_道後] 湯 - よい: 0.0513
[04_道後] メニュー - 少ない: 0.0455
[04_道後] 品数 - 少ない: 0.0455
[04_道後] 立地 - よい: 0.0380
[04_道後] サウナ - よい: 0.0370
[04_道後] ジュース - よい: 0.0345
[04_道後] ジュース - おいしい: 0.0345
[04_道後] ビジネス - にくい: 0.0345
[04_道後] お湯 - よい: 0.0286
[04_道後] 少ない - よい: 0.0263
[04_道後] 無料 - よい: 0.0244
[04_道後] 場所 - にくい: 0.0192
[04_道後] よい - ホテル: 0.0148
[04_道後] よい - 小さい: 0.0074
[04_道後] よい - よい: 0.0074
[04_道後] ホテル - ほしい: 0.0044

[05_湯布院] 感じ - 良い: 0.1548
[05_湯布院] 景色 - 良い: 0.1277
[05_湯布院] すごい - 良い: 0.1176
[05_湯布院] 味 - 良い: 0.0769
[05_湯布院] 味 - 美味しい: 0.0769
[05_湯布院] 雰囲気 - 良い: 0.0755
[05_湯布院] ご飯 - 美味しい: 0.0735
[05_湯布院] いい - 宿: 0.0700
[05_湯布院] 皆さん - 良い: 0.0508
[05_湯布院] いい - 感じ: 0.0500
[05_湯布院] 気持ち - よい: 0.0462
[05_湯布院] 気持ち - 良い: 0.0462
[05_湯布院] おいしい - よい: 0.0455
[05_湯布院] 景色 - 最高: 0.0426
[05_湯布院] 方々 - 皆さん: 0.0426
[05_湯布院] 多い - 美味しい: 0.0426
[05_湯布院] お湯 - 最高: 0.0408
[05_湯布院] お湯 - 良い: 0.0408
[05_湯布院] 最高 - 宿: 0.0400
[05_湯布院] 優しい - 宿: 0.0400

[B_ビジネス] 繁華 - 近い: 0.0400
[B_ビジネス] 電車 - やすい: 0.0290
[B_ビジネス] 洗い場 - 狭い: 0.0135
[B_ビジネス] ツイン - 狭い: 0.0122
[B_ビジネス] 高い - ホテル: 0.0099
[B_ビジネス] やすい - ホテル: 0.0098
[B_ビジネス] エレベーター - 狭い: 0.0069
[B_ビジネス] 大きい - やすい: 0.0066
[B_ビジネス] 清潔 - やすい: 0.0065
[B_ビジネス] 新しい - 快適: 0.0057
[B_ビジネス] ルーム - 狭い: 0.0054
[B_ビジネス] ビジネス - にくい: 0.0053
[B_ビジネス] ビジネス - やすい: 0.0053
[B_ビジネス] 駅 - 近い: 0.0050
[B_ビジネス] コンビニ - 近い: 0.0044
[B_ビジネス] 荷物 - ありがたい: 0.0041
[B_ビジネス] トイレ - 別: 0.0033
[B_ビジネス] やすい - 立地: 0.0033
[B_ビジネス] やすい - ありがたい: 0.0033
[B_ビジネス] 清潔 - 価格: 0.0032

[06_札幌] 洗い場 - 寒い: 0.0556
[06_札幌] 次 - 連: 0.0556
[06_札幌] やすい - ホテル: 0.0556
[06_札幌] 寒い - ほしい: 0.0476
[06_札幌] 高い - ホテル: 0.0465
[06_札幌] パン - 欲しい: 0.0435
[06_札幌] 水 - 欲しい: 0.0357
[06_札幌] 駅 - 近い: 0.0354
[06_札幌] 欲しい - ホテル: 0.0333
[06_札幌] 清潔 - やすい: 0.0328
[06_札幌] コーヒー - ほしい: 0.0312
[06_札幌] ルーム - 広い: 0.0278
[06_札幌] ビジネス - やすい: 0.0278
[06_札幌] コンビニ - 近い: 0.0270
[06_札幌] 新しい - 快適: 0.0222
[06_札幌] 荷物 - ありがたい: 0.0213
[06_札幌] ベッド - 広い: 0.0196
[06_札幌] やすい - 立地: 0.0185
[06_札幌] やすい - ありがたい: 0.0185
[06_札幌] 清潔 - 価格: 0.0164

[07_名古屋] 繁華 - 近い: 0.0968
[07_名古屋] 安い - よい: 0.0732
[07_名古屋] 広め - 嬉しい: 0.0714
[07_名古屋] 寝心地 - よい: 0.0714
[07_名古屋] 洗い場 - 狭い: 0.0588
[07_名古屋] コーヒー - 嬉しい: 0.0588
[07_名古屋] タイプ - 嬉しい: 0.0556
[07_名古屋] 高い - ホテル: 0.0444
[07_名古屋] 立地 - よい: 0.0427
[07_名古屋] 近い - よい: 0.0417
[07_名古屋] やすい - ホテル: 0.0390
[07_名古屋] メニュー - 嬉しい: 0.0385
[07_名古屋] エレベーター - 狭い: 0.0357
[07_名古屋] 大きい - やすい: 0.0333
[07_名古屋] ルーム - 狭い: 0.0294
[07_名古屋] コーヒー - よい: 0.0294
[07_名古屋] 清潔 - やすい: 0.0290
[07_名古屋] 駅 - 近い: 0.0263
[07_名古屋] 新しい - 快適: 0.0263
[07_名古屋] やすい - よい: 0.0260

[08_東京] コーヒー - 嬉しい: 0.0741
[08_東京] 値段 - ホテル: 0.0455
[08_東京] 高い - ホテル: 0.0444
[08_東京] コーヒー - ほしい: 0.0370
[08_東京] エレベーター - 狭い: 0.0370
[08_東京] 早い - ほしい: 0.0357
[08_東京] ドリンク - 嬉しい: 0.0345
[08_東京] 女性 - ほしい: 0.0333
[08_東京] ルーム - 狭い: 0.0323
[08_東京] ビジネス - にくい: 0.0250
[08_東京] 値段 - 高い: 0.0227
[08_東京] 値段 - ビジネス: 0.0227
[08_東京] 駅 - 近い: 0.0190
[08_東京] 他 - ホテル: 0.0189
[08_東京] トイレ - 別: 0.0185
[08_東京] 嬉しい - アメニティ: 0.0169
[08_東京] コンビニ - 近い: 0.0165
[08_東京] 場所 - 快適: 0.0159
[08_東京] 場所 - にくい: 0.0159
[08_東京] 狭い - にくい: 0.0128

[09_大阪] 電車 - やすい: 0.1111
[09_大阪] 洗い場 - 狭い: 0.0625
[09_大阪] ツイン - 狭い: 0.0526
[09_大阪] パン - 欲しい: 0.0476
[09_大阪] 値段 - ホテル: 0.0455
[09_大阪] 次 - 連: 0.0455
[09_大阪] やすい - ホテル: 0.0448
[09_大阪] コーヒー - ほしい: 0.0357
[09_大阪] 欲しい - ホテル: 0.0333
[09_大阪] サウナ - 欲しい: 0.0294
[09_大阪] ビジネス - やすい: 0.0286
[09_大阪] 水 - 欲しい: 0.0278
[09_大阪] 大きい - やすい: 0.0278
[09_大阪] 新しい - 快適: 0.0256
[09_大阪] エレベーター - 狭い: 0.0250
[09_大阪] エレベーター - 欲しい: 0.0250
[09_大阪] ルーム - 狭い: 0.0244
[09_大阪] 駅 - 近い: 0.0242
[09_大阪] コンビニ - 近い: 0.0230
[09_大阪] 値段 - ビジネス: 0.0227

[10_福岡] いい - ホテル: 0.1358
[10_福岡] 電車 - やすい: 0.1111
[10_福岡] 洗い場 - 狭い: 0.0556
[10_福岡] 目的 - 近い: 0.0526
[10_福岡] やすい - ホテル: 0.0517
[10_福岡] ロビー - ない: 0.0400
[10_福岡] エレベーター - 狭い: 0.0370
[10_福岡] 圏内 - ない: 0.0370
[10_福岡] 距離 - 近い: 0.0345
[10_福岡] 印象 - ホテル: 0.0323
[10_福岡] 大きい - やすい: 0.0312
[10_福岡] 清潔 - やすい: 0.0286
[10_福岡] 駅 - 近い: 0.0261
[10_福岡] コンビニ - 近い: 0.0256
[10_福岡] 荷物 - ありがたい: 0.0244
[10_福岡] ルーム - 狭い: 0.0238
[10_福岡] やすい - ありがたい: 0.0172
[10_福岡] やすい - 立地: 0.0172
[10_福岡] ない - やすい: 0.0164
[10_福岡] 徒歩 - ない: 0.0147

No description has been provided for this image

1.3 高評価のエリアに倣って、低評価のエリアを改善するプランを提案する¶

1.3.1 対照的な2エリアを選択する¶

In [13]:
# コーディングルール
coding_pos = ["良い","美味しい","広い","多い","素晴らしい","嬉しい","気持ちよい","楽しい","近い","大きい","気持ち良い","温かい","早い","優しい","新しい","暖かい","快い","明るい","美しい","可愛い","満足"]
coding_neg = ["古い","無い","高い","悪い","小さい","狭い","少ない","寒い","遅い","熱い","欲しい","暑い","冷たい","遠い","臭い","暗い","うるさい","ない","無い","残念","改善","不満"]
In [14]:
# DataFrame を初期化する
cross_1000_ps_df = cross_1000_df.copy()
cross_1000_ps_df['ポジ'] = 0
cross_1000_ps_df['ネガ'] = 0
cross_1000_ps_df['総合1-2'] = 0
cross_1000_ps_df['総合4-5'] = 0

# コーディングルールを適用する (ポジ・ネガ)
pos_index = docs_df['表層'].str.contains("|".join(coding_pos))
neg_index = docs_df['表層'].str.contains("|".join(coding_neg))
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(docs_df.loc[pos_index, '文書ID']), 'ポジ'] = 1
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(docs_df.loc[neg_index, '文書ID']), 'ネガ'] = 1

# コーディングルールを適用する (総合評価)
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(all_df[all_df['総合'] <=2].index), '総合1-2'] = 1
cross_1000_ps_df.loc[cross_1000_ps_df.index.get_level_values('文書ID').isin(all_df[all_df['総合'] >=4].index), '総合4-5'] = 1
cross_1000_ps_df = cross_1000_ps_df[['ポジ','ネガ','総合1-2','総合4-5']]

# DataFrame を表示する
print(cross_1000_ps_df.shape)
display(cross_1000_ps_df)
(9914, 4)
表層 ポジ ネガ 総合1-2 総合4-5
カテゴリー エリア 文書ID
A_レジャー 01_登別 1 1 0 0 1
2 1 1 0 1
3 0 0 0 1
4 1 0 1 0
5 1 1 0 1
... ... ... ... ... ... ...
B_ビジネス 10_福岡 9996 1 0 0 1
9997 0 1 0 1
9998 0 0 0 1
9999 1 0 0 1
10000 1 0 0 0

9914 rows × 4 columns

In [15]:
# 「カテゴリー」のクロス集計と「エリア」のクロス集計を連結する
aggregate_ps_df = pd.concat(
    [
        cross_1000_ps_df.groupby(level='カテゴリー').sum(),
        cross_1000_ps_df.groupby(level='エリア').sum()
    ]
)

# DataFrame を表示する
print(aggregate_ps_df.shape)
display(aggregate_ps_df)
(12, 4)
表層 ポジ ネガ 総合1-2 総合4-5
A_レジャー 3798 2297 282 4279
B_ビジネス 3230 2105 289 4032
01_登別 729 472 69 822
02_草津 803 497 56 843
03_箱根 793 495 63 857
04_道後 668 405 47 854
05_湯布院 805 428 47 903
06_札幌 666 399 48 825
07_名古屋 642 435 40 828
08_東京 641 421 70 776
09_大阪 655 409 52 815
10_福岡 626 441 79 788
In [16]:
# 必要ライブラリのインポート
import mca

# ライブラリ mca による対応分析
ncols = aggregate_ps_df.shape[1]
mca_ben = mca.MCA(aggregate_ps_df, ncols=ncols, benzecri=False)

# 行方向および列方向の値を取り出す
row_coord = mca_ben.fs_r(N=2)
col_coord = mca_ben.fs_c(N=2)

# 固有値を求める
eigenvalues = mca_ben.L
total = np.sum(eigenvalues)
# 寄与率を求める
explained_inertia = 100 * eigenvalues / total

# 行方向および列方向のラベルを取得する
row_labels = aggregate_ps_df.index
col_labels = aggregate_ps_df.columns

# プロットする
gssm_utils.plot_coresp(row_coord, col_coord, row_labels, col_labels, explained_inertia)
No description has been provided for this image

1.3.2 ポジティブ意見の共起ネットワーク図を作成する¶

In [17]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline

# コーディングルール
coding_or = coding_pos

# サブルーチン
def sort_and_plot(name, group):

    # 「カテゴリー」ごとに Jaccard 係数でソートする
    sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]

    # Jaccard 係数Top 75語をソートして抽出する
    group_cross_df = group.iloc[:,sorted_columns]

    # 共起行列を作成する
    X = group_cross_df.values
    X = csc_matrix(X)
    Xc = (X.T * X)
    Xc = np.triu(Xc.toarray())

    # コーディングルールで絞り込む
    index = docs_df['表層'].str.contains("|".join(coding_or))
    group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]

    # 共起行列を DataFrame に整える
    group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)

    # 共起行列の中身を Jaccard 係数に入れ替える
    group_jaccard_df = gssm_utils.jaccard_coef(group_cooccur_df, group_cross_df)

    # 抽出語の出現回数を取得する
    word_counts = group_cross_df.sum(axis=0).values

    # プロットする
    ax = fig.add_subplot(4, 3, i+1)
    pyvis_plot = gssm_utils.plot_cooccur_network_with_code_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], coding_or, pyvis=True, name=f"pos-{name}.html")
    ax.set_title(name)
    display(pyvis_plot)

# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))

i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
    # サブルーチンを呼ぶ
    sort_and_plot(name, group)
    i += 1

    # エリアごとのループ
    for sub_name, sub_group in group.groupby(level='エリア'):
        # サブルーチンを呼ぶ
        sort_and_plot(sub_name, sub_group)
        i += 1

# プロットの仕上げ
plt.tight_layout()
plt.show()
pos-A_レジャー.html
pos-01_登別.html
pos-02_草津.html
pos-03_箱根.html
pos-04_道後.html
pos-05_湯布院.html
pos-B_ビジネス.html
pos-06_札幌.html
pos-07_名古屋.html
pos-08_東京.html
pos-09_大阪.html
pos-10_福岡.html
No description has been provided for this image

1.3.3 ポジティブ意見の係り受けネットワーク図を作成する¶

In [18]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline

# コーディングルール
coding_or = coding_pos

# サブルーチン
def sort_and_plot(name, group, pos):

    # 「カテゴリー」ごとに Jaccard 係数でソートする
    sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]

    # Jaccard 係数Top 75語をソートして抽出する
    group_cross_df = group.iloc[:,sorted_columns]

    # 抽出語の出現回数を取得する
    word_counts = group_cross_df.sum(axis=0).values

    # 共起行列を作成する
    X = group_cross_df.values
    X = csc_matrix(X)
    Xc = (X.T * X)
    Xc = np.triu(Xc.toarray())

    # コーディングルールで絞り込む
    index = docs_df['表層'].str.contains("|".join(coding_or))
    group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]

    # 共起行列を DataFrame に整える
    group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)

    # 係り受け行列(条件付き確率)を初期化する (共起行列と同じ形)
    Xd = np.zeros(group_cooccur_df.shape)

    # 係り受けペアを係り受け行列(条件付き確率)に変換する
    for (f,t), v in pair_counts.items():

        # 係り元と係り先の両方が列に含まれる
        columns = list(group_cooccur_df.columns)
        if f in columns and t in columns:
            i = columns.index(f)
            j = columns.index(t)

            # 条件付き確率(係り受け頻度/係り先出現回数)を求める
            Xd[i,j] = v / word_counts[i]

    # 係り受け行列(条件付き確率)を DataFrame 型に整える
    group_dependency_df = pd.DataFrame(Xd, columns=group_cross_df.columns, index=group_cross_df.columns)

    # プロットする
    ax = fig.add_subplot(4, 3, pos+1)
    pyvis_plot = gssm_utils.plot_cooccur_network_with_code_ax(ax, group_dependency_df, word_counts, np.sort(group_dependency_df.values.reshape(-1))[::-1][120], coding_or, pyvis=False, name=f"pos-{name}.html")
    ax.set_title(name)
    # display(pyvis_plot)

    # 係り受けペアを条件付き確率で降順にソートし20個表示する
    Xc = group_dependency_df.values
    words = group_dependency_df.columns
    flat_indices = np.argsort(Xc.ravel())[::-1][:20]
    row_indices, col_indices = np.unravel_index(flat_indices, Xc.shape)
    for idx in range(len(flat_indices)):
        value = Xc[row_indices[idx], col_indices[idx]]
        print(f"[{name}] {words[row_indices[idx]]} - {words[col_indices[idx]]}: {value:.04f}")
    print()

# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))

i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
    # サブルーチンを呼ぶ
    sort_and_plot(name, group, i)
    i += 1

    # エリアごとのループ
    for sub_name, sub_group in group.groupby(level='エリア'):
        # サブルーチンを呼ぶ
        sort_and_plot(sub_name, sub_group, i)
        i += 1

# プロットの仕上げ
plt.tight_layout()
plt.show()
[A_レジャー] 感じ - 良い: 0.0343
[A_レジャー] 景色 - 良い: 0.0335
[A_レジャー] すごい - 良い: 0.0244
[A_レジャー] 雰囲気 - 良い: 0.0233
[A_レジャー] ご飯 - 美味しい: 0.0181
[A_レジャー] 気持ち - 良い: 0.0162
[A_レジャー] 気持ち - よい: 0.0162
[A_レジャー] 味 - 良い: 0.0160
[A_レジャー] 味 - 美味しい: 0.0160
[A_レジャー] いい - 宿: 0.0157
[A_レジャー] 皆さん - 良い: 0.0147
[A_レジャー] 優しい - 宿: 0.0136
[A_レジャー] 浴場 - 良い: 0.0124
[A_レジャー] おいしい - よい: 0.0114
[A_レジャー] いい - 感じ: 0.0112
[A_レジャー] 景色 - 最高: 0.0112
[A_レジャー] 最高 - 宿: 0.0109
[A_レジャー] 方々 - 皆さん: 0.0102
[A_レジャー] 多い - 美味しい: 0.0081
[A_レジャー] お湯 - 良い: 0.0080

[01_登別] 気分 - 悪い: 0.2857
[01_登別] 感じ - 良い: 0.1757
[01_登別] 景色 - 良い: 0.1176
[01_登別] すごい - 良い: 0.1053
[01_登別] ご飯 - 美味しい: 0.0943
[01_登別] 味 - 良い: 0.0857
[01_登別] 味 - 美味しい: 0.0857
[01_登別] 眺め - 最高: 0.0800
[01_登別] 眺め - 良い: 0.0800
[01_登別] いい - 宿: 0.0722
[01_登別] 浴場 - 良い: 0.0600
[01_登別] 最高 - 宿: 0.0547
[01_登別] いい - 感じ: 0.0515
[01_登別] 風呂 - 良い: 0.0405
[01_登別] 内容 - 悪い: 0.0400
[01_登別] 内容 - 良い: 0.0400
[01_登別] お湯 - 良い: 0.0400
[01_登別] 思い出 - 良い: 0.0400
[01_登別] お湯 - 最高: 0.0400
[01_登別] 景色 - 最高: 0.0392

[02_草津] 感じ - 良い: 0.1413
[02_草津] 雰囲気 - 良い: 0.1176
[02_草津] すごい - 良い: 0.1053
[02_草津] いい - 宿: 0.0897
[02_草津] ご飯 - 美味しい: 0.0862
[02_草津] 味 - 美味しい: 0.0769
[02_草津] 味 - 良い: 0.0769
[02_草津] いい - 感じ: 0.0641
[02_草津] 浴場 - 良い: 0.0625
[02_草津] 皆さん - 良い: 0.0588
[02_草津] おいしい - よい: 0.0577
[02_草津] 最高 - 宿: 0.0473
[02_草津] 方々 - 皆さん: 0.0444
[02_草津] 露天風呂 - 最高: 0.0396
[02_草津] 多い - 美味しい: 0.0364
[02_草津] 種類 - 少ない: 0.0339
[02_草津] 風呂 - 良い: 0.0319
[02_草津] 広い - 良い: 0.0315
[02_草津] お湯 - 良い: 0.0312
[02_草津] お湯 - 最高: 0.0312

[03_箱根] 感じ - 良い: 0.1857
[03_箱根] 景色 - 良い: 0.1250
[03_箱根] 雰囲気 - 良い: 0.0833
[03_箱根] 気持ち - 良い: 0.0750
[03_箱根] 気持ち - よい: 0.0750
[03_箱根] いい - 宿: 0.0737
[03_箱根] 最高 - 宿: 0.0660
[03_箱根] 皆さん - 良い: 0.0652
[03_箱根] ご飯 - 美味しい: 0.0633
[03_箱根] デザート - 美味しい: 0.0571
[03_箱根] 味 - 良い: 0.0556
[03_箱根] 優しい - 宿: 0.0556
[03_箱根] 味 - 美味しい: 0.0556
[03_箱根] おいしい - よい: 0.0536
[03_箱根] いい - 感じ: 0.0526
[03_箱根] 浴場 - 良い: 0.0488
[03_箱根] アメニティ - 良い: 0.0429
[03_箱根] 湯 - よい: 0.0417
[03_箱根] 景色 - 最高: 0.0417
[03_箱根] お湯 - 最高: 0.0392

[04_道後] ごはん - おいしい: 0.1429
[04_道後] バスタオル - ほしい: 0.0833
[04_道後] おいしい - よい: 0.0667
[04_道後] 浴衣 - にくい: 0.0588
[04_道後] 湯 - よい: 0.0513
[04_道後] メニュー - 少ない: 0.0455
[04_道後] 品数 - 少ない: 0.0455
[04_道後] 立地 - よい: 0.0380
[04_道後] サウナ - よい: 0.0370
[04_道後] ジュース - よい: 0.0345
[04_道後] ジュース - おいしい: 0.0345
[04_道後] ビジネス - にくい: 0.0345
[04_道後] お湯 - よい: 0.0286
[04_道後] 少ない - よい: 0.0263
[04_道後] 無料 - よい: 0.0244
[04_道後] 場所 - にくい: 0.0192
[04_道後] よい - ホテル: 0.0148
[04_道後] よい - 小さい: 0.0074
[04_道後] よい - よい: 0.0074
[04_道後] ホテル - ほしい: 0.0044

[05_湯布院] 感じ - 良い: 0.1548
[05_湯布院] 景色 - 良い: 0.1277
[05_湯布院] すごい - 良い: 0.1176
[05_湯布院] 味 - 良い: 0.0769
[05_湯布院] 味 - 美味しい: 0.0769
[05_湯布院] 雰囲気 - 良い: 0.0755
[05_湯布院] ご飯 - 美味しい: 0.0735
[05_湯布院] いい - 宿: 0.0700
[05_湯布院] 皆さん - 良い: 0.0508
[05_湯布院] いい - 感じ: 0.0500
[05_湯布院] 気持ち - よい: 0.0462
[05_湯布院] 気持ち - 良い: 0.0462
[05_湯布院] おいしい - よい: 0.0455
[05_湯布院] 景色 - 最高: 0.0426
[05_湯布院] 方々 - 皆さん: 0.0426
[05_湯布院] 多い - 美味しい: 0.0426
[05_湯布院] お湯 - 最高: 0.0408
[05_湯布院] お湯 - 良い: 0.0408
[05_湯布院] 最高 - 宿: 0.0400
[05_湯布院] 優しい - 宿: 0.0400

[B_ビジネス] 繁華 - 近い: 0.0400
[B_ビジネス] 電車 - やすい: 0.0290
[B_ビジネス] 洗い場 - 狭い: 0.0135
[B_ビジネス] ツイン - 狭い: 0.0122
[B_ビジネス] 高い - ホテル: 0.0099
[B_ビジネス] やすい - ホテル: 0.0098
[B_ビジネス] エレベーター - 狭い: 0.0069
[B_ビジネス] 大きい - やすい: 0.0066
[B_ビジネス] 清潔 - やすい: 0.0065
[B_ビジネス] 新しい - 快適: 0.0057
[B_ビジネス] ルーム - 狭い: 0.0054
[B_ビジネス] ビジネス - にくい: 0.0053
[B_ビジネス] ビジネス - やすい: 0.0053
[B_ビジネス] 駅 - 近い: 0.0050
[B_ビジネス] コンビニ - 近い: 0.0044
[B_ビジネス] 荷物 - ありがたい: 0.0041
[B_ビジネス] トイレ - 別: 0.0033
[B_ビジネス] やすい - 立地: 0.0033
[B_ビジネス] やすい - ありがたい: 0.0033
[B_ビジネス] 清潔 - 価格: 0.0032

[06_札幌] 洗い場 - 寒い: 0.0556
[06_札幌] 次 - 連: 0.0556
[06_札幌] やすい - ホテル: 0.0556
[06_札幌] 寒い - ほしい: 0.0476
[06_札幌] 高い - ホテル: 0.0465
[06_札幌] パン - 欲しい: 0.0435
[06_札幌] 水 - 欲しい: 0.0357
[06_札幌] 駅 - 近い: 0.0354
[06_札幌] 欲しい - ホテル: 0.0333
[06_札幌] 清潔 - やすい: 0.0328
[06_札幌] コーヒー - ほしい: 0.0312
[06_札幌] ルーム - 広い: 0.0278
[06_札幌] ビジネス - やすい: 0.0278
[06_札幌] コンビニ - 近い: 0.0270
[06_札幌] 新しい - 快適: 0.0222
[06_札幌] 荷物 - ありがたい: 0.0213
[06_札幌] ベッド - 広い: 0.0196
[06_札幌] やすい - 立地: 0.0185
[06_札幌] やすい - ありがたい: 0.0185
[06_札幌] 清潔 - 価格: 0.0164

[07_名古屋] 繁華 - 近い: 0.0968
[07_名古屋] 安い - よい: 0.0732
[07_名古屋] 広め - 嬉しい: 0.0714
[07_名古屋] 寝心地 - よい: 0.0714
[07_名古屋] 洗い場 - 狭い: 0.0588
[07_名古屋] コーヒー - 嬉しい: 0.0588
[07_名古屋] タイプ - 嬉しい: 0.0556
[07_名古屋] 高い - ホテル: 0.0444
[07_名古屋] 立地 - よい: 0.0427
[07_名古屋] 近い - よい: 0.0417
[07_名古屋] やすい - ホテル: 0.0390
[07_名古屋] メニュー - 嬉しい: 0.0385
[07_名古屋] エレベーター - 狭い: 0.0357
[07_名古屋] 大きい - やすい: 0.0333
[07_名古屋] ルーム - 狭い: 0.0294
[07_名古屋] コーヒー - よい: 0.0294
[07_名古屋] 清潔 - やすい: 0.0290
[07_名古屋] 駅 - 近い: 0.0263
[07_名古屋] 新しい - 快適: 0.0263
[07_名古屋] やすい - よい: 0.0260

[08_東京] コーヒー - 嬉しい: 0.0741
[08_東京] 値段 - ホテル: 0.0455
[08_東京] 高い - ホテル: 0.0444
[08_東京] コーヒー - ほしい: 0.0370
[08_東京] エレベーター - 狭い: 0.0370
[08_東京] 早い - ほしい: 0.0357
[08_東京] ドリンク - 嬉しい: 0.0345
[08_東京] 女性 - ほしい: 0.0333
[08_東京] ルーム - 狭い: 0.0323
[08_東京] ビジネス - にくい: 0.0250
[08_東京] 値段 - 高い: 0.0227
[08_東京] 値段 - ビジネス: 0.0227
[08_東京] 駅 - 近い: 0.0190
[08_東京] 他 - ホテル: 0.0189
[08_東京] トイレ - 別: 0.0185
[08_東京] 嬉しい - アメニティ: 0.0169
[08_東京] コンビニ - 近い: 0.0165
[08_東京] 場所 - 快適: 0.0159
[08_東京] 場所 - にくい: 0.0159
[08_東京] 狭い - にくい: 0.0128

[09_大阪] 電車 - やすい: 0.1111
[09_大阪] 洗い場 - 狭い: 0.0625
[09_大阪] ツイン - 狭い: 0.0526
[09_大阪] パン - 欲しい: 0.0476
[09_大阪] 値段 - ホテル: 0.0455
[09_大阪] 次 - 連: 0.0455
[09_大阪] やすい - ホテル: 0.0448
[09_大阪] コーヒー - ほしい: 0.0357
[09_大阪] 欲しい - ホテル: 0.0333
[09_大阪] サウナ - 欲しい: 0.0294
[09_大阪] ビジネス - やすい: 0.0286
[09_大阪] 水 - 欲しい: 0.0278
[09_大阪] 大きい - やすい: 0.0278
[09_大阪] 新しい - 快適: 0.0256
[09_大阪] エレベーター - 狭い: 0.0250
[09_大阪] エレベーター - 欲しい: 0.0250
[09_大阪] ルーム - 狭い: 0.0244
[09_大阪] 駅 - 近い: 0.0242
[09_大阪] コンビニ - 近い: 0.0230
[09_大阪] 値段 - ビジネス: 0.0227

[10_福岡] いい - ホテル: 0.1358
[10_福岡] 電車 - やすい: 0.1111
[10_福岡] 洗い場 - 狭い: 0.0556
[10_福岡] 目的 - 近い: 0.0526
[10_福岡] やすい - ホテル: 0.0517
[10_福岡] ロビー - ない: 0.0400
[10_福岡] エレベーター - 狭い: 0.0370
[10_福岡] 圏内 - ない: 0.0370
[10_福岡] 距離 - 近い: 0.0345
[10_福岡] 印象 - ホテル: 0.0323
[10_福岡] 大きい - やすい: 0.0312
[10_福岡] 清潔 - やすい: 0.0286
[10_福岡] 駅 - 近い: 0.0261
[10_福岡] コンビニ - 近い: 0.0256
[10_福岡] 荷物 - ありがたい: 0.0244
[10_福岡] ルーム - 狭い: 0.0238
[10_福岡] やすい - ありがたい: 0.0172
[10_福岡] やすい - 立地: 0.0172
[10_福岡] ない - やすい: 0.0164
[10_福岡] 徒歩 - ない: 0.0147

No description has been provided for this image

1.3.4 ネガティブ意見の共起ネットワーク図を作成する¶

In [19]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline

# コーディングルール
coding_or = coding_neg

# サブルーチン
def sort_and_plot(name, group):

    # 「カテゴリー」ごとに Jaccard 係数でソートする
    sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]

    # Jaccard 係数Top 75語をソートして抽出する
    group_cross_df = group.iloc[:,sorted_columns]

    # 共起行列を作成する
    X = group_cross_df.values
    X = csc_matrix(X)
    Xc = (X.T * X)
    Xc = np.triu(Xc.toarray())

    # コーディングルールで絞り込む
    index = docs_df['表層'].str.contains("|".join(coding_or))
    group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]

    # 共起行列を DataFrame に整える
    group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)

    # 共起行列の中身を Jaccard 係数に入れ替える
    group_jaccard_df = gssm_utils.jaccard_coef(group_cooccur_df, group_cross_df)

    # 抽出語の出現回数を取得する
    word_counts = group_cross_df.sum(axis=0).values

    # プロットする
    ax = fig.add_subplot(4, 3, i+1)
    pyvis_plot = gssm_utils.plot_cooccur_network_with_code_ax(ax, group_jaccard_df, word_counts, np.sort(group_jaccard_df.values.reshape(-1))[::-1][120], coding_or, pyvis=True, name=f"neg-{name}.html")
    ax.set_title(name)
    display(pyvis_plot)

# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))

i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
    # サブルーチンを呼ぶ
    sort_and_plot(name, group)
    i += 1

    # エリアごとのループ
    for sub_name, sub_group in group.groupby(level='エリア'):
        # サブルーチンを呼ぶ
        sort_and_plot(sub_name, sub_group)
        i += 1

# プロットの仕上げ
plt.tight_layout()
plt.show()
neg-A_レジャー.html
neg-01_登別.html
neg-02_草津.html
neg-03_箱根.html
neg-04_道後.html
neg-05_湯布院.html
neg-B_ビジネス.html
neg-06_札幌.html
neg-07_名古屋.html
neg-08_東京.html
neg-09_大阪.html
neg-10_福岡.html
No description has been provided for this image

1.3.5 ネガティブ意見係り受けネットワーク図を作成する¶

In [20]:
# 必要ライブラリのインポート
from scipy.sparse import csc_matrix
import matplotlib.pyplot as plt
%matplotlib inline

# コーディングルール
coding_or = coding_neg

# サブルーチン
def sort_and_plot(name, group, pos):

    # 「カテゴリー」ごとに Jaccard 係数でソートする
    sorted_columns = np.argsort(jaccard_attrs_df.loc[name].values)[::-1][:75]

    # Jaccard 係数Top 75語をソートして抽出する
    group_cross_df = group.iloc[:,sorted_columns]

    # 抽出語の出現回数を取得する
    word_counts = group_cross_df.sum(axis=0).values

    # 共起行列を作成する
    X = group_cross_df.values
    X = csc_matrix(X)
    Xc = (X.T * X)
    Xc = np.triu(Xc.toarray())

    # コーディングルールで絞り込む
    index = docs_df['表層'].str.contains("|".join(coding_or))
    group_cross_df = group_cross_df[group_cross_df.index.get_level_values('文書ID').isin(docs_df.loc[index, '文書ID'])]

    # 共起行列を DataFrame に整える
    group_cooccur_df = pd.DataFrame(Xc, columns=group_cross_df.columns, index=group_cross_df.columns)

    # 係り受け行列(条件付き確率)を初期化する (共起行列と同じ形)
    Xd = np.zeros(group_cooccur_df.shape)

    # 係り受けペアを係り受け行列(条件付き確率)に変換する
    for (f,t), v in pair_counts.items():

        # 係り元と係り先の両方が列に含まれる
        columns = list(group_cooccur_df.columns)
        if f in columns and t in columns:
            i = columns.index(f)
            j = columns.index(t)

            # 条件付き確率(係り受け頻度/係り先出現回数)を求める
            Xd[i,j] = v / word_counts[i]

    # 係り受け行列(条件付き確率)を DataFrame 型に整える
    group_dependency_df = pd.DataFrame(Xd, columns=group_cross_df.columns, index=group_cross_df.columns)

    # プロットする
    ax = fig.add_subplot(4, 3, pos+1)
    pyvis_plot = gssm_utils.plot_cooccur_network_with_code_ax(ax, group_dependency_df, word_counts, np.sort(group_dependency_df.values.reshape(-1))[::-1][120], coding_or, pyvis=False, name=f"pos-{name}.html")
    ax.set_title(name)
    # display(pyvis_plot)

    # 係り受けペアを条件付き確率で降順にソートし20個表示する
    Xc = group_dependency_df.values
    words = group_dependency_df.columns
    flat_indices = np.argsort(Xc.ravel())[::-1][:20]
    row_indices, col_indices = np.unravel_index(flat_indices, Xc.shape)
    for idx in range(len(flat_indices)):
        value = Xc[row_indices[idx], col_indices[idx]]
        print(f"[{name}] {words[row_indices[idx]]} - {words[col_indices[idx]]}: {value:.04f}")
    print()

# プロットの準備
# fig = plt.figure(figsize=(20, 28))
fig = plt.figure(figsize=(28, 28))

i = 0
# カテゴリごとのループ
for name, group in cross_1000_df.groupby(level='カテゴリー'):
    # サブルーチンを呼ぶ
    sort_and_plot(name, group, i)
    i += 1

    # エリアごとのループ
    for sub_name, sub_group in group.groupby(level='エリア'):
        # サブルーチンを呼ぶ
        sort_and_plot(sub_name, sub_group, i)
        i += 1

# プロットの仕上げ
plt.tight_layout()
plt.show()
[A_レジャー] 感じ - 良い: 0.0343
[A_レジャー] 景色 - 良い: 0.0335
[A_レジャー] すごい - 良い: 0.0244
[A_レジャー] 雰囲気 - 良い: 0.0233
[A_レジャー] ご飯 - 美味しい: 0.0181
[A_レジャー] 気持ち - 良い: 0.0162
[A_レジャー] 気持ち - よい: 0.0162
[A_レジャー] 味 - 良い: 0.0160
[A_レジャー] 味 - 美味しい: 0.0160
[A_レジャー] いい - 宿: 0.0157
[A_レジャー] 皆さん - 良い: 0.0147
[A_レジャー] 優しい - 宿: 0.0136
[A_レジャー] 浴場 - 良い: 0.0124
[A_レジャー] おいしい - よい: 0.0114
[A_レジャー] いい - 感じ: 0.0112
[A_レジャー] 景色 - 最高: 0.0112
[A_レジャー] 最高 - 宿: 0.0109
[A_レジャー] 方々 - 皆さん: 0.0102
[A_レジャー] 多い - 美味しい: 0.0081
[A_レジャー] お湯 - 良い: 0.0080

[01_登別] 気分 - 悪い: 0.2857
[01_登別] 感じ - 良い: 0.1757
[01_登別] 景色 - 良い: 0.1176
[01_登別] すごい - 良い: 0.1053
[01_登別] ご飯 - 美味しい: 0.0943
[01_登別] 味 - 良い: 0.0857
[01_登別] 味 - 美味しい: 0.0857
[01_登別] 眺め - 最高: 0.0800
[01_登別] 眺め - 良い: 0.0800
[01_登別] いい - 宿: 0.0722
[01_登別] 浴場 - 良い: 0.0600
[01_登別] 最高 - 宿: 0.0547
[01_登別] いい - 感じ: 0.0515
[01_登別] 風呂 - 良い: 0.0405
[01_登別] 内容 - 悪い: 0.0400
[01_登別] 内容 - 良い: 0.0400
[01_登別] お湯 - 良い: 0.0400
[01_登別] 思い出 - 良い: 0.0400
[01_登別] お湯 - 最高: 0.0400
[01_登別] 景色 - 最高: 0.0392

[02_草津] 感じ - 良い: 0.1413
[02_草津] 雰囲気 - 良い: 0.1176
[02_草津] すごい - 良い: 0.1053
[02_草津] いい - 宿: 0.0897
[02_草津] ご飯 - 美味しい: 0.0862
[02_草津] 味 - 美味しい: 0.0769
[02_草津] 味 - 良い: 0.0769
[02_草津] いい - 感じ: 0.0641
[02_草津] 浴場 - 良い: 0.0625
[02_草津] 皆さん - 良い: 0.0588
[02_草津] おいしい - よい: 0.0577
[02_草津] 最高 - 宿: 0.0473
[02_草津] 方々 - 皆さん: 0.0444
[02_草津] 露天風呂 - 最高: 0.0396
[02_草津] 多い - 美味しい: 0.0364
[02_草津] 種類 - 少ない: 0.0339
[02_草津] 風呂 - 良い: 0.0319
[02_草津] 広い - 良い: 0.0315
[02_草津] お湯 - 良い: 0.0312
[02_草津] お湯 - 最高: 0.0312

[03_箱根] 感じ - 良い: 0.1857
[03_箱根] 景色 - 良い: 0.1250
[03_箱根] 雰囲気 - 良い: 0.0833
[03_箱根] 気持ち - 良い: 0.0750
[03_箱根] 気持ち - よい: 0.0750
[03_箱根] いい - 宿: 0.0737
[03_箱根] 最高 - 宿: 0.0660
[03_箱根] 皆さん - 良い: 0.0652
[03_箱根] ご飯 - 美味しい: 0.0633
[03_箱根] デザート - 美味しい: 0.0571
[03_箱根] 味 - 良い: 0.0556
[03_箱根] 優しい - 宿: 0.0556
[03_箱根] 味 - 美味しい: 0.0556
[03_箱根] おいしい - よい: 0.0536
[03_箱根] いい - 感じ: 0.0526
[03_箱根] 浴場 - 良い: 0.0488
[03_箱根] アメニティ - 良い: 0.0429
[03_箱根] 湯 - よい: 0.0417
[03_箱根] 景色 - 最高: 0.0417
[03_箱根] お湯 - 最高: 0.0392

[04_道後] ごはん - おいしい: 0.1429
[04_道後] バスタオル - ほしい: 0.0833
[04_道後] おいしい - よい: 0.0667
[04_道後] 浴衣 - にくい: 0.0588
[04_道後] 湯 - よい: 0.0513
[04_道後] メニュー - 少ない: 0.0455
[04_道後] 品数 - 少ない: 0.0455
[04_道後] 立地 - よい: 0.0380
[04_道後] サウナ - よい: 0.0370
[04_道後] ジュース - よい: 0.0345
[04_道後] ジュース - おいしい: 0.0345
[04_道後] ビジネス - にくい: 0.0345
[04_道後] お湯 - よい: 0.0286
[04_道後] 少ない - よい: 0.0263
[04_道後] 無料 - よい: 0.0244
[04_道後] 場所 - にくい: 0.0192
[04_道後] よい - ホテル: 0.0148
[04_道後] よい - 小さい: 0.0074
[04_道後] よい - よい: 0.0074
[04_道後] ホテル - ほしい: 0.0044

[05_湯布院] 感じ - 良い: 0.1548
[05_湯布院] 景色 - 良い: 0.1277
[05_湯布院] すごい - 良い: 0.1176
[05_湯布院] 味 - 良い: 0.0769
[05_湯布院] 味 - 美味しい: 0.0769
[05_湯布院] 雰囲気 - 良い: 0.0755
[05_湯布院] ご飯 - 美味しい: 0.0735
[05_湯布院] いい - 宿: 0.0700
[05_湯布院] 皆さん - 良い: 0.0508
[05_湯布院] いい - 感じ: 0.0500
[05_湯布院] 気持ち - よい: 0.0462
[05_湯布院] 気持ち - 良い: 0.0462
[05_湯布院] おいしい - よい: 0.0455
[05_湯布院] 景色 - 最高: 0.0426
[05_湯布院] 方々 - 皆さん: 0.0426
[05_湯布院] 多い - 美味しい: 0.0426
[05_湯布院] お湯 - 最高: 0.0408
[05_湯布院] お湯 - 良い: 0.0408
[05_湯布院] 最高 - 宿: 0.0400
[05_湯布院] 優しい - 宿: 0.0400

[B_ビジネス] 繁華 - 近い: 0.0400
[B_ビジネス] 電車 - やすい: 0.0290
[B_ビジネス] 洗い場 - 狭い: 0.0135
[B_ビジネス] ツイン - 狭い: 0.0122
[B_ビジネス] 高い - ホテル: 0.0099
[B_ビジネス] やすい - ホテル: 0.0098
[B_ビジネス] エレベーター - 狭い: 0.0069
[B_ビジネス] 大きい - やすい: 0.0066
[B_ビジネス] 清潔 - やすい: 0.0065
[B_ビジネス] 新しい - 快適: 0.0057
[B_ビジネス] ルーム - 狭い: 0.0054
[B_ビジネス] ビジネス - にくい: 0.0053
[B_ビジネス] ビジネス - やすい: 0.0053
[B_ビジネス] 駅 - 近い: 0.0050
[B_ビジネス] コンビニ - 近い: 0.0044
[B_ビジネス] 荷物 - ありがたい: 0.0041
[B_ビジネス] トイレ - 別: 0.0033
[B_ビジネス] やすい - 立地: 0.0033
[B_ビジネス] やすい - ありがたい: 0.0033
[B_ビジネス] 清潔 - 価格: 0.0032

[06_札幌] 洗い場 - 寒い: 0.0556
[06_札幌] 次 - 連: 0.0556
[06_札幌] やすい - ホテル: 0.0556
[06_札幌] 寒い - ほしい: 0.0476
[06_札幌] 高い - ホテル: 0.0465
[06_札幌] パン - 欲しい: 0.0435
[06_札幌] 水 - 欲しい: 0.0357
[06_札幌] 駅 - 近い: 0.0354
[06_札幌] 欲しい - ホテル: 0.0333
[06_札幌] 清潔 - やすい: 0.0328
[06_札幌] コーヒー - ほしい: 0.0312
[06_札幌] ルーム - 広い: 0.0278
[06_札幌] ビジネス - やすい: 0.0278
[06_札幌] コンビニ - 近い: 0.0270
[06_札幌] 新しい - 快適: 0.0222
[06_札幌] 荷物 - ありがたい: 0.0213
[06_札幌] ベッド - 広い: 0.0196
[06_札幌] やすい - 立地: 0.0185
[06_札幌] やすい - ありがたい: 0.0185
[06_札幌] 清潔 - 価格: 0.0164

[07_名古屋] 繁華 - 近い: 0.0968
[07_名古屋] 安い - よい: 0.0732
[07_名古屋] 広め - 嬉しい: 0.0714
[07_名古屋] 寝心地 - よい: 0.0714
[07_名古屋] 洗い場 - 狭い: 0.0588
[07_名古屋] コーヒー - 嬉しい: 0.0588
[07_名古屋] タイプ - 嬉しい: 0.0556
[07_名古屋] 高い - ホテル: 0.0444
[07_名古屋] 立地 - よい: 0.0427
[07_名古屋] 近い - よい: 0.0417
[07_名古屋] やすい - ホテル: 0.0390
[07_名古屋] メニュー - 嬉しい: 0.0385
[07_名古屋] エレベーター - 狭い: 0.0357
[07_名古屋] 大きい - やすい: 0.0333
[07_名古屋] ルーム - 狭い: 0.0294
[07_名古屋] コーヒー - よい: 0.0294
[07_名古屋] 清潔 - やすい: 0.0290
[07_名古屋] 駅 - 近い: 0.0263
[07_名古屋] 新しい - 快適: 0.0263
[07_名古屋] やすい - よい: 0.0260

[08_東京] コーヒー - 嬉しい: 0.0741
[08_東京] 値段 - ホテル: 0.0455
[08_東京] 高い - ホテル: 0.0444
[08_東京] コーヒー - ほしい: 0.0370
[08_東京] エレベーター - 狭い: 0.0370
[08_東京] 早い - ほしい: 0.0357
[08_東京] ドリンク - 嬉しい: 0.0345
[08_東京] 女性 - ほしい: 0.0333
[08_東京] ルーム - 狭い: 0.0323
[08_東京] ビジネス - にくい: 0.0250
[08_東京] 値段 - 高い: 0.0227
[08_東京] 値段 - ビジネス: 0.0227
[08_東京] 駅 - 近い: 0.0190
[08_東京] 他 - ホテル: 0.0189
[08_東京] トイレ - 別: 0.0185
[08_東京] 嬉しい - アメニティ: 0.0169
[08_東京] コンビニ - 近い: 0.0165
[08_東京] 場所 - 快適: 0.0159
[08_東京] 場所 - にくい: 0.0159
[08_東京] 狭い - にくい: 0.0128

[09_大阪] 電車 - やすい: 0.1111
[09_大阪] 洗い場 - 狭い: 0.0625
[09_大阪] ツイン - 狭い: 0.0526
[09_大阪] パン - 欲しい: 0.0476
[09_大阪] 値段 - ホテル: 0.0455
[09_大阪] 次 - 連: 0.0455
[09_大阪] やすい - ホテル: 0.0448
[09_大阪] コーヒー - ほしい: 0.0357
[09_大阪] 欲しい - ホテル: 0.0333
[09_大阪] サウナ - 欲しい: 0.0294
[09_大阪] ビジネス - やすい: 0.0286
[09_大阪] 水 - 欲しい: 0.0278
[09_大阪] 大きい - やすい: 0.0278
[09_大阪] 新しい - 快適: 0.0256
[09_大阪] エレベーター - 狭い: 0.0250
[09_大阪] エレベーター - 欲しい: 0.0250
[09_大阪] ルーム - 狭い: 0.0244
[09_大阪] 駅 - 近い: 0.0242
[09_大阪] コンビニ - 近い: 0.0230
[09_大阪] 値段 - ビジネス: 0.0227

[10_福岡] いい - ホテル: 0.1358
[10_福岡] 電車 - やすい: 0.1111
[10_福岡] 洗い場 - 狭い: 0.0556
[10_福岡] 目的 - 近い: 0.0526
[10_福岡] やすい - ホテル: 0.0517
[10_福岡] ロビー - ない: 0.0400
[10_福岡] エレベーター - 狭い: 0.0370
[10_福岡] 圏内 - ない: 0.0370
[10_福岡] 距離 - 近い: 0.0345
[10_福岡] 印象 - ホテル: 0.0323
[10_福岡] 大きい - やすい: 0.0312
[10_福岡] 清潔 - やすい: 0.0286
[10_福岡] 駅 - 近い: 0.0261
[10_福岡] コンビニ - 近い: 0.0256
[10_福岡] 荷物 - ありがたい: 0.0244
[10_福岡] ルーム - 狭い: 0.0238
[10_福岡] やすい - ありがたい: 0.0172
[10_福岡] やすい - 立地: 0.0172
[10_福岡] ない - やすい: 0.0164
[10_福岡] 徒歩 - ない: 0.0147

No description has been provided for this image

1.3.4 本文の参照 (エリアで絞る)¶

「登別」と「道後」で「すばらしい」という単語が含まれている口コミを表示する

In [21]:
# 検索条件
search_index = \
    all_df['エリア'].isin(['01_登別', '05_道後']) & \
    (all_df['コメント'].str.contains('素晴らしい') | all_df['コメント'].str.contains('すばらしい'))

# 検索する
result_df = all_df[search_index]

# DataFrame を表示する
print(result_df.shape)
display(result_df.head())

# CSV に保存する
result_df.to_csv("output-1.csv", header=True)
(41, 18)
カテゴリー エリア 施設番号 施設名 コメント 総合 サービス 立地 部屋 設備・アメニティ 風呂 食事 旅行の目的 同伴者 宿泊年月 投稿者 年代 性別
68 A_レジャー 01_登別 51198 望楼NOGUCHI登別 ホテルスタッフの対応素晴らしいですね!夕飯の和とフレンチのコース料理美味しかったですよー 4 3 4 3 3.0 5.0 5.0 レジャー 恋人 45413 投稿者 na na
114 A_レジャー 01_登別 172346 ザ・レイクスイート湖の栖(グランベルホテルズ&リゾーツ) 部屋のベッドの固さ、枕は最適でした。どこのメーカーか調べるの忘れました。アメニティ他用意され... 5 5 5 5 5.0 5.0 3.0 レジャー 家族 45170 投稿者 na na
148 A_レジャー 01_登別 12568 登別温泉 ホテル まほろば 何度宿泊しても素晴らしいホテルです。初めてGREEN Terrace で食事しましたが…リバ... 5 5 5 5 5.0 5.0 5.0 レジャー 恋人 45108 T.Y0922 50代 女性
162 A_レジャー 01_登別 40708 虎杖浜温泉 ホテル いずみ 2度目の利用でした。とにかく素晴らしい泉質の温泉や風呂からの眺めが楽しめる宿です。施設や部屋... 5 5 5 3 3.0 5.0 3.0 レジャー 一人 45323 投稿者 na na
198 A_レジャー 01_登別 164980 森の湯 山静館 口コミ通りの素晴らしい温泉です。登別温泉街とは源泉が異なり、硫黄臭が無くトロトロです。浴室に... 5 5 4 4 3.0 5.0 5.0 レジャー 家族 45231 とみい社長 60代 男性

「東京」と「福岡」で「うるさい」という単語が含まれている口コミを表示する

In [22]:
# 検索条件
search_index = \
    all_df['エリア'].isin(['08_東京', '10_福岡']) & \
    all_df['コメント'].str.contains('うるさい')

# 検索する
result_df = all_df[search_index]

# DataFrame を表示する
print(result_df.shape)
display(result_df.head())

# CSV に保存する
result_df.to_csv("output-2.csv", header=True)
(9, 18)
カテゴリー エリア 施設番号 施設名 コメント 総合 サービス 立地 部屋 設備・アメニティ 風呂 食事 旅行の目的 同伴者 宿泊年月 投稿者 年代 性別
7064 B_ビジネス 08_東京 164596 IMANO TOKYO GINZA HOSTEL 部屋は10階の6人部屋女性専用フロアです。17:30にチェックインし、荷物を置いて友達と遊び... 4 4 5 4 4.0 3.0 NaN その他 一人 44927 投稿者 na na
7215 B_ビジネス 08_東京 2024 レンブラントスタイル東京西葛西 部屋がとにかく暗すぎです。Wi-Fiが途切れ最悪でした。空気清浄機がうるさい騒音で全く効果な... 2 3 5 1 1.0 1.0 NaN レジャー 家族 44958 投稿者 na na
7217 B_ビジネス 08_東京 2447 ダイヤモンドホテル エアコンディショナーがうるさいです。コストからして、立地以外には利点がない。 2 3 4 2 2.0 3.0 3.0 ビジネス 一人 45323 投稿者 na na
7327 B_ビジネス 08_東京 179995 unito CHIYODA 場所、入口が分かりにくい。夜中にアジア系外人の電話うるさい。部屋の通路狭すぎコスパは1980... 3 2 3 2 3.0 3.0 NaN ビジネス 一人 45292 木村次郎応援団 60代 男性
7492 B_ビジネス 08_東京 18248 ビジネスホテル福千 駅近でJRとメトロがありアクセス良好です。線路が近いので電車の音はうるさいです。耳栓の持参を... 4 3 5 3 3.0 3.0 NaN レジャー 一人 45292 はたけ9495 40代 男性